﻿using System;
using System.Collections.Generic;
using System.Text;
using ActiproSoftware.SyntaxEditor;
using ActiproSoftware.SyntaxEditor.Addons.DotNet.Ast;

namespace WindowsUserControl.Codes
{
    /// <summary>
    /// Provides a semantic parser for the <c>Simple</c> language.
    /// </summary>
    internal class SimpleSemanticParser : ActiproSoftware.SyntaxEditor.ParserGenerator.RecursiveDescentSemanticParser
    {

        private CompilationUnit compilationUnit;
        private System.Int32 curlyBraceLevel;

        /// <summary>
        /// Initializes a new instance of the <c>SimpleSemanticParser</c> class.
        /// </summary>
        /// <param name="lexicalParser">The <see cref="ActiproSoftware.SyntaxEditor.ParserGenerator.IRecursiveDescentLexicalParser"/> to use for lexical parsing.</param>
        public SimpleSemanticParser(ActiproSoftware.SyntaxEditor.ParserGenerator.IRecursiveDescentLexicalParser lexicalParser) : base(lexicalParser) { }

        /// <summary>
        /// Advances to the next <see cref="IToken"/>.
        /// </summary>
        /// <returns>
        /// The <see cref="IToken"/> that was read.
        /// </returns>
        protected override IToken AdvanceToNext()
        {
            IToken token = base.AdvanceToNext();
            if (!this.TokenIsLanguageChange(token))
            {
                switch (token.ID)
                {
                    case SimpleTokenID.OpenCurlyBrace:
                        curlyBraceLevel++;
                        break;
                    case SimpleTokenID.CloseCurlyBrace:
                        curlyBraceLevel--;
                        break;
                }
            }
            return token;
        }

        /// <summary>
        /// Advances to (or past) the next matching close curly brace.
        /// </summary>
        /// <param name="movePast">Whether to move past the close curly brace.</param>
        /// <param name="openCurlyBraceLevel">The level of the open curly brace.</param>	
        private void AdvanceToNextCloseCurlyBrace(System.Int32 openCurlyBraceLevel, System.Boolean movePast)
        {
            while (!this.IsAtEnd)
            {
                this.AdvanceToNext(SimpleTokenID.OpenCurlyBrace, SimpleTokenID.CloseCurlyBrace);
                if ((this.TokenIs(this.LookAheadToken, SimpleTokenID.CloseCurlyBrace)) && (curlyBraceLevel <= openCurlyBraceLevel + 1))
                    break;
                this.AdvanceToNext();
            }
            if ((movePast) && (this.TokenIs(this.LookAheadToken, SimpleTokenID.CloseCurlyBrace)))
                this.AdvanceToNext();
        }

        /// <summary>
        /// Gets the <see cref="CompilationUnit"/> that was parsed.
        /// </summary>
        /// <value>The <see cref="CompilationUnit"/> that was parsed.</value>
        public CompilationUnit CompilationUnit
        {
            get
            {
                return compilationUnit;
            }
        }

        /// <summary>
        /// Reports a syntax error.
        /// </summary>
        /// <param name="textRange">The <see cref="TextRange"/> of the error.</param>
        /// <param name="message">The error message.</param>
        protected override void ReportSyntaxError(TextRange textRange, System.String message)
        {
            // Don't allow multiple errors at the same offset
            if ((compilationUnit.SyntaxErrors.Count > 0) && (((SyntaxError)compilationUnit.SyntaxErrors[compilationUnit.SyntaxErrors.Count - 1]).TextRange.StartOffset == textRange.StartOffset))
                return;

            compilationUnit.SyntaxErrors.Add(new SyntaxError(textRange, message));
        }

        /// <summary>
        /// Parses the document and generates a document object model.
        /// </summary>
        public void Parse()
        {
            this.MatchCompilationUnit();
        }

        /// <summary>
        /// Matches a <c>CompilationUnit</c> non-terminal.
        /// </summary>
        /// <returns><c>true</c> if the <c>CompilationUnit</c> was matched successfully; otherwise, <c>false</c>.</returns>
        /// <remarks>
        /// The non-terminal can start with: <c>Function</c>.
        /// </remarks>
        protected virtual bool MatchCompilationUnit()
        {
            curlyBraceLevel = 0;
            compilationUnit = new CompilationUnit();
            compilationUnit.StartOffset = this.LookAheadToken.StartOffset;
            System.Boolean errorReported = false;
            while (!this.IsAtEnd)
            {
                if (this.TokenIs(this.LookAheadToken, SimpleTokenID.Function))
                {
                    errorReported = false;
                    while (this.TokenIs(this.LookAheadToken, SimpleTokenID.Function))
                    {
                        this.MatchFunctionDeclaration();
                    }
                }
                else
                {
                    // Error recovery:  Advance to the next token since nothing was matched
                    if (!errorReported)
                    {
                        this.ReportSyntaxError("Function declaration expected.");
                        errorReported = true;
                    }
                    this.AdvanceToNext();
                }
            }
            compilationUnit.EndOffset = this.LookAheadToken.EndOffset;
            return true;
        }

        /// <summary>
        /// Matches a <c>FunctionDeclaration</c> non-terminal.
        /// </summary>
        /// <returns><c>true</c> if the <c>FunctionDeclaration</c> was matched successfully; otherwise, <c>false</c>.</returns>
        /// <remarks>
        /// The non-terminal can start with: <c>Function</c>.
        /// </remarks>
        protected virtual bool MatchFunctionDeclaration()
        {
            FunctionDeclaration functionDeclaration = new FunctionDeclaration();
            functionDeclaration.StartOffset = this.LookAheadToken.StartOffset;
            Identifier name;
            AstNodeList parameterList = null;
            Statement blockStatement;
            if (!this.Match(SimpleTokenID.Function))
                return false;
            if (!this.MatchIdentifier(out name))
                return false;
            if (!this.Match(SimpleTokenID.OpenParenthesis))
                return false;
            functionDeclaration.Name = name;
            compilationUnit.Functions.Add(functionDeclaration);
            if (this.TokenIs(this.LookAheadToken, SimpleTokenID.Identifier))
            {
                if (!this.MatchFunctionParameterList(out parameterList))
                    return false;
            }
            this.Match(SimpleTokenID.CloseParenthesis);
            if (parameterList != null)
                functionDeclaration.Parameters.AddRange(parameterList.ToArray());
            functionDeclaration.BlockStartOffset = this.LookAheadToken.StartOffset;
            this.MatchBlock(out blockStatement);
            functionDeclaration.BlockStatement = (BlockStatement)blockStatement;
            functionDeclaration.BlockEndOffset = this.Token.EndOffset;
            functionDeclaration.EndOffset = this.Token.EndOffset;
            return true;
        }

        /// <summary>
        /// Matches a <c>Identifier</c> non-terminal.
        /// </summary>
        /// <returns><c>true</c> if the <c>Identifier</c> was matched successfully; otherwise, <c>false</c>.</returns>
        /// <remarks>
        /// The non-terminal can start with: <c>Identifier</c>.
        /// </remarks>
        protected virtual bool MatchIdentifier(out Identifier identifier)
        {
            identifier = null;
            if (!this.Match(SimpleTokenID.Identifier))
                return false;
            identifier = new Identifier(this.TokenText, this.Token.TextRange);
            return true;
        }

        /// <summary>
        /// Matches a <c>FunctionParameterList</c> non-terminal.
        /// </summary>
        /// <returns><c>true</c> if the <c>FunctionParameterList</c> was matched successfully; otherwise, <c>false</c>.</returns>
        /// <remarks>
        /// The non-terminal can start with: <c>Identifier</c>.
        /// </remarks>
        protected virtual bool MatchFunctionParameterList(out AstNodeList parameterList)
        {
            parameterList = new AstNodeList(null);
            Identifier parameterName;
            if (!this.MatchIdentifier(out parameterName))
                return false;
            else
            {
                parameterList.Add(parameterName);
            }
            while (this.TokenIs(this.LookAheadToken, SimpleTokenID.Comma))
            {
                if (!this.Match(SimpleTokenID.Comma))
                    return false;
                if (this.MatchIdentifier(out parameterName))
                {
                    parameterList.Add(parameterName);
                }
            }
            return true;
        }

        /// <summary>
        /// Matches a <c>Statement</c> non-terminal.
        /// </summary>
        /// <returns><c>true</c> if the <c>Statement</c> was matched successfully; otherwise, <c>false</c>.</returns>
        /// <remarks>
        /// The non-terminal can start with: <c>Identifier</c>, <c>SemiColon</c>, <c>Var</c>, <c>Return</c>, <c>OpenCurlyBrace</c>.
        /// </remarks>
        protected virtual bool MatchStatement(out Statement statement)
        {
            statement = null;
            System.Int32 startOffset = this.LookAheadToken.StartOffset;
            if (this.TokenIs(this.LookAheadToken, SimpleTokenID.OpenCurlyBrace))
            {
                if (!this.MatchBlock(out statement))
                    return false;
            }
            else if (this.TokenIs(this.LookAheadToken, SimpleTokenID.SemiColon))
            {
                // Empty statement
                if (!this.Match(SimpleTokenID.SemiColon))
                    return false;
                statement = new EmptyStatement(this.Token.TextRange);
            }
            else if (this.TokenIs(this.LookAheadToken, SimpleTokenID.Var))
            {
                // Variable declaration statement
                Identifier variableName = null;
                if (!this.Match(SimpleTokenID.Var))
                    return false;
                if (!this.MatchIdentifier(out variableName))
                    return false;
                this.Match(SimpleTokenID.SemiColon);
                statement = new VariableDeclarationStatement(variableName, new TextRange(startOffset, this.Token.EndOffset));
            }
            else if (this.TokenIs(this.LookAheadToken, SimpleTokenID.Identifier))
            {
                // Assignment statement
                Identifier variableName = null;
                Expression expression = null;
                if (!this.MatchIdentifier(out variableName))
                    return false;
                if (!this.Match(SimpleTokenID.Assignment))
                    return false;
                if (!this.MatchExpression(out expression))
                    return false;
                this.Match(SimpleTokenID.SemiColon);
                statement = new AssignmentStatement(variableName, expression, new TextRange(startOffset, this.Token.EndOffset));
            }
            else if (this.TokenIs(this.LookAheadToken, SimpleTokenID.Return))
            {
                // Return statement
                Expression expression = null;
                if (!this.Match(SimpleTokenID.Return))
                    return false;
                if (!this.MatchExpression(out expression))
                    return false;
                this.Match(SimpleTokenID.SemiColon);
                statement = new ReturnStatement(expression, new TextRange(startOffset, this.Token.EndOffset));
            }
            else
                return false;
            return true;
        }

        /// <summary>
        /// Matches a <c>Block</c> non-terminal.
        /// </summary>
        /// <returns><c>true</c> if the <c>Block</c> was matched successfully; otherwise, <c>false</c>.</returns>
        /// <remarks>
        /// The non-terminal can start with: <c>OpenCurlyBrace</c>.
        /// </remarks>
        protected virtual bool MatchBlock(out Statement block)
        {
            block = new BlockStatement();
            block.StartOffset = this.LookAheadToken.StartOffset;
            Statement statement;
            System.Int32 statementCurlyBraceLevel = curlyBraceLevel;
            if (!this.Match(SimpleTokenID.OpenCurlyBrace))
                return false;
            System.Boolean errorReported = false;
            while (!this.IsAtEnd)
            {
                if ((this.TokenIs(this.LookAheadToken, SimpleTokenID.CloseCurlyBrace)) && (curlyBraceLevel == statementCurlyBraceLevel + 1))
                    break;
                else if (this.IsInMultiMatchSet(0, this.LookAheadToken))
                {
                    errorReported = false;
                    while (this.IsInMultiMatchSet(1, this.LookAheadToken))
                    {
                        if (!this.MatchStatement(out statement))
                        {
                            if (!this.TokenIs(this.LookAheadToken, SimpleTokenID.CloseCurlyBrace)) this.AdvanceToNext();
                        }
                        else
                        {
                            ((BlockStatement)block).Statements.Add(statement);
                        }
                    }
                }
                else
                {
                    // Error recovery:  Advance to the next token since nothing was matched
                    if (!errorReported)
                    {
                        this.ReportSyntaxError("Statement expected.");
                        errorReported = true;
                    }
                    this.AdvanceToNext();
                }
            }
            this.Match(SimpleTokenID.CloseCurlyBrace);
            block.EndOffset = this.Token.EndOffset;
            return true;
        }

        /// <summary>
        /// Matches a <c>Expression</c> non-terminal.
        /// </summary>
        /// <returns><c>true</c> if the <c>Expression</c> was matched successfully; otherwise, <c>false</c>.</returns>
        /// <remarks>
        /// The non-terminal can start with: <c>OpenParenthesis</c>, <c>Identifier</c>, <c>Number</c>.
        /// </remarks>
        protected virtual bool MatchExpression(out Expression expression)
        {
            if (!this.MatchEqualityExpression(out expression))
                return false;
            return true;
        }

        /// <summary>
        /// Matches a <c>EqualityExpression</c> non-terminal.
        /// </summary>
        /// <returns><c>true</c> if the <c>EqualityExpression</c> was matched successfully; otherwise, <c>false</c>.</returns>
        /// <remarks>
        /// The non-terminal can start with: <c>OpenParenthesis</c>, <c>Identifier</c>, <c>Number</c>.
        /// </remarks>
        protected virtual bool MatchEqualityExpression(out Expression expression)
        {
            System.Int32 startOffset = this.LookAheadToken.StartOffset;
            Expression rightExpression;
            if (!this.MatchAdditiveExpression(out expression))
                return false;
            while ((this.TokenIs(this.LookAheadToken, SimpleTokenID.Equality)) || (this.TokenIs(this.LookAheadToken, SimpleTokenID.Inequality)))
            {
                OperatorType operatorType = OperatorType.None;
                if (this.TokenIs(this.LookAheadToken, SimpleTokenID.Equality))
                {
                    if (!this.Match(SimpleTokenID.Equality))
                        return false;
                    else
                    {
                        operatorType = OperatorType.Equality;
                    }
                }
                else if (this.TokenIs(this.LookAheadToken, SimpleTokenID.Inequality))
                {
                    if (!this.Match(SimpleTokenID.Inequality))
                        return false;
                    else
                    {
                        operatorType = OperatorType.Inequality;
                    }
                }
                else
                    return false;
                if (!this.MatchEqualityExpression(out rightExpression))
                    return false;
                expression = new BinaryExpression(operatorType, expression, rightExpression, new TextRange(startOffset, this.Token.EndOffset));
            }
            return true;
        }

        /// <summary>
        /// Matches a <c>AdditiveExpression</c> non-terminal.
        /// </summary>
        /// <returns><c>true</c> if the <c>AdditiveExpression</c> was matched successfully; otherwise, <c>false</c>.</returns>
        /// <remarks>
        /// The non-terminal can start with: <c>OpenParenthesis</c>, <c>Identifier</c>, <c>Number</c>.
        /// </remarks>
        protected virtual bool MatchAdditiveExpression(out Expression expression)
        {
            System.Int32 startOffset = this.LookAheadToken.StartOffset;
            Expression rightExpression;
            if (!this.MatchMultiplicativeExpression(out expression))
                return false;
            while ((this.TokenIs(this.LookAheadToken, SimpleTokenID.Addition)) || (this.TokenIs(this.LookAheadToken, SimpleTokenID.Subtraction)))
            {
                OperatorType operatorType = OperatorType.None;
                if (this.TokenIs(this.LookAheadToken, SimpleTokenID.Addition))
                {
                    if (!this.Match(SimpleTokenID.Addition))
                        return false;
                    else
                    {
                        operatorType = OperatorType.Addition;
                    }
                }
                else if (this.TokenIs(this.LookAheadToken, SimpleTokenID.Subtraction))
                {
                    if (!this.Match(SimpleTokenID.Subtraction))
                        return false;
                    else
                    {
                        operatorType = OperatorType.Subtraction;
                    }
                }
                else
                    return false;
                if (!this.MatchAdditiveExpression(out rightExpression))
                    return false;
                expression = new BinaryExpression(operatorType, expression, rightExpression, new TextRange(startOffset, this.Token.EndOffset));
            }
            return true;
        }

        /// <summary>
        /// Matches a <c>MultiplicativeExpression</c> non-terminal.
        /// </summary>
        /// <returns><c>true</c> if the <c>MultiplicativeExpression</c> was matched successfully; otherwise, <c>false</c>.</returns>
        /// <remarks>
        /// The non-terminal can start with: <c>OpenParenthesis</c>, <c>Identifier</c>, <c>Number</c>.
        /// </remarks>
        protected virtual bool MatchMultiplicativeExpression(out Expression expression)
        {
            System.Int32 startOffset = this.LookAheadToken.StartOffset;
            Expression rightExpression;
            if (!this.MatchPrimaryExpression(out expression))
                return false;
            while ((this.TokenIs(this.LookAheadToken, SimpleTokenID.Multiplication)) || (this.TokenIs(this.LookAheadToken, SimpleTokenID.Division)))
            {
                OperatorType operatorType = OperatorType.None;
                if (this.TokenIs(this.LookAheadToken, SimpleTokenID.Multiplication))
                {
                    if (!this.Match(SimpleTokenID.Multiplication))
                        return false;
                    else
                    {
                        operatorType = OperatorType.Multiplication;
                    }
                }
                else if (this.TokenIs(this.LookAheadToken, SimpleTokenID.Division))
                {
                    if (!this.Match(SimpleTokenID.Division))
                        return false;
                    else
                    {
                        operatorType = OperatorType.Division;
                    }
                }
                else
                    return false;
                if (!this.MatchMultiplicativeExpression(out rightExpression))
                    return false;
                expression = new BinaryExpression(operatorType, expression, rightExpression, new TextRange(startOffset, this.Token.EndOffset));
            }
            return true;
        }

        /// <summary>
        /// Matches a <c>PrimaryExpression</c> non-terminal.
        /// </summary>
        /// <returns><c>true</c> if the <c>PrimaryExpression</c> was matched successfully; otherwise, <c>false</c>.</returns>
        /// <remarks>
        /// The non-terminal can start with: <c>OpenParenthesis</c>, <c>Identifier</c>, <c>Number</c>.
        /// </remarks>
        protected virtual bool MatchPrimaryExpression(out Expression expression)
        {
            expression = null;
            System.Int32 startOffset = this.LookAheadToken.StartOffset;
            if (this.TokenIs(this.LookAheadToken, SimpleTokenID.Number))
            {
                if (!this.Match(SimpleTokenID.Number))
                    return false;
                else
                {
                    expression = new NumberExpression(Convert.ToInt32(this.TokenText), this.Token.TextRange);
                }
            }
            else if (this.TokenIs(this.LookAheadToken, SimpleTokenID.Identifier))
            {
                if (!this.Match(SimpleTokenID.Identifier))
                    return false;
                // This shows one way to reduce grammar ambiguity
                if (!this.TokenIs(this.LookAheadToken, SimpleTokenID.OpenParenthesis))
                {
                    // The next token is not an open parenthesis... must be a simple name
                    expression = new SimpleName(this.TokenText, this.Token.TextRange);
                    return true;
                }

                expression = new FunctionAccessExpression(new Identifier(this.TokenText, this.Token.TextRange));
                AstNodeList argumentList = null;
                if (!this.Match(SimpleTokenID.OpenParenthesis))
                    return false;
                if ((this.TokenIs(this.LookAheadToken, SimpleTokenID.OpenParenthesis)) || (this.TokenIs(this.LookAheadToken, SimpleTokenID.Identifier)) || (this.TokenIs(this.LookAheadToken, SimpleTokenID.Number)))
                {
                    if (!this.MatchFunctionArgumentList(out argumentList))
                        return false;
                }
                this.Match(SimpleTokenID.CloseParenthesis);
                if (argumentList != null)
                    ((FunctionAccessExpression)expression).Parameters.AddRange(argumentList.ToArray());
                expression.TextRange = new TextRange(startOffset, this.Token.EndOffset);
            }
            else if (this.TokenIs(this.LookAheadToken, SimpleTokenID.OpenParenthesis))
            {
                if (!this.Match(SimpleTokenID.OpenParenthesis))
                    return false;
                if (!this.MatchExpression(out expression))
                    return false;
                if (!this.Match(SimpleTokenID.CloseParenthesis))
                    return false;
                expression = new ParenthesizedExpression(expression, new TextRange(startOffset, this.Token.EndOffset));
            }
            else
                return false;
            return true;
        }

        /// <summary>
        /// Matches a <c>FunctionArgumentList</c> non-terminal.
        /// </summary>
        /// <returns><c>true</c> if the <c>FunctionArgumentList</c> was matched successfully; otherwise, <c>false</c>.</returns>
        /// <remarks>
        /// The non-terminal can start with: <c>OpenParenthesis</c>, <c>Identifier</c>, <c>Number</c>.
        /// </remarks>
        protected virtual bool MatchFunctionArgumentList(out AstNodeList argumentList)
        {
            argumentList = new AstNodeList(null);
            Expression argument;
            if (!this.MatchExpression(out argument))
                return false;
            else
            {
                argumentList.Add(argument);
            }
            while (this.TokenIs(this.LookAheadToken, SimpleTokenID.Comma))
            {
                if (!this.Match(SimpleTokenID.Comma))
                    return false;
                if (this.MatchExpression(out argument))
                {
                    argumentList.Add(argument);
                }
            }
            return true;
        }

        /// <summary>
        /// Gets the multi-match sets array.
        /// </summary>
        /// <value>The multi-match sets array.</value>
        protected override bool[,] MultiMatchSets
        {
            get
            {
                return multiMatchSets;
            }
        }

        private const bool Y = true;
        private const bool n = false;
        private static bool[,] multiMatchSets = {
			// 0: Identifier SemiColon Var Return OpenCurlyBrace 
			{n,n,n,n,n,n,n,n,n,Y,n,n,Y,Y,n,n,Y,n,n,n,n,Y,n,n,n,n,n,n,n,n,n},
			// 1: Identifier SemiColon Var Return OpenCurlyBrace 
			{n,n,n,n,n,n,n,n,n,Y,n,n,Y,Y,n,n,Y,n,n,n,n,Y,n,n,n,n,n,n,n,n,n}
		};

    }
}
